// File StateMachine.h
//
// Purpose: Contains the state machine
//
// Author : Bryson King (edited)
// Original Code Provided by: Mat Buckland 2002 (fup@ai-junkie.com)
// Date : 18 March 2013
// --------------------------------------------------------------
#ifndef STATEMACHINE_H
#define STATEMACHINE_H

#include <string>
#include "AbstractState.h"
#include "Telegram.h"

template <class entity_type>
class CStateMachine
{
public:
	CStateMachine(entity_type* owner) : m_pOwner(owner),
		m_pCurrentState(nullptr),
		m_pPreviousState(nullptr),
		m_pGlobalState(nullptr)
	{}
	virtual ~CStateMachine() {}

	//use these methods to initialize the FSM
	void SetCurrentState(CState<entity_type>* s) {m_pCurrentState = s;}
	void SetGlobalState(CState<entity_type>* s) {m_pGlobalState = s;}
	void SetPreviousState(CState<entity_type>* s) {m_pPreviousState = s;}
  
	//call this to update the FSM
	void  Update() const
	{
		//if a global state exists, call its execute method, else do nothing
		if(m_pGlobalState)
			m_pGlobalState->Execute(m_pOwner);
		//same for the current state
		if (m_pCurrentState)
			m_pCurrentState->Execute(m_pOwner);
	}

	bool  HandleMessage(const Telegram& msg) const
	{
		//first see if the current state is valid and that it can handle
		//the message
		if (m_pCurrentState && m_pCurrentState->OnMessage(m_pOwner, msg))
		{
			return true;
		}
  
		//if not, and if a global state has been implemented, send 
		//the message to the global state
		if (m_pGlobalState && m_pGlobalState->OnMessage(m_pOwner, msg))
		{
			return true;
		}

		return false;
	}

	//change to a new state
	void  ChangeState(CState<entity_type>* pNewState)
	{
		#ifdef DEBUG_SECTION
		//make sure the val is equal to or greater than the next available ID
			if(!(pNewState))
				std::cout << std::endl << "<CPlayerStateMachine::ChangeState>:trying to assign null state to current";
		#endif

		//keep a record of the previous state
		m_pPreviousState = m_pCurrentState;

		//call the exit method of the existing state
		m_pCurrentState->Exit(m_pOwner);

		//change state to the new state
		m_pCurrentState = pNewState;

		//call the entry method of the new state
		m_pCurrentState->Enter(m_pOwner);
	}

	//change state back to the previous state
	void  RevertToPreviousState()
	{
		ChangeState(m_pPreviousState);
	}

	//returns true if the current state's type is equal to the type of the
	//class passed as a parameter. 
	bool  isInState(const CState<entity_type>& st) const
	{
		if (typeid(*m_pCurrentState) == typeid(st)) 
			return true;
		return false;
	}

	CState<entity_type>* CurrentState()  const {return m_pCurrentState;}
	CState<entity_type>* GlobalState()   const {return m_pGlobalState;}
	CState<entity_type>* PreviousState() const {return m_pPreviousState;}

	//only ever used during debugging to grab the name of the current state
	std::string GetNameOfCurrentState() const
	{
		std::string s(typeid(*m_pCurrentState).name());

		//remove the 'class ' part from the front of the string
		if (s.size() > 5)
		{
			s.erase(0, 6);
		}

		return s;
	}

private:
	//a pointer to the agent that owns this instance
	entity_type* m_pOwner;

	//the current state of the entity
	CState<entity_type>* m_pCurrentState;

	//a record of the last state the agent was in
	CState<entity_type>* m_pPreviousState;

	//this is called every time the FSM is updated
	CState<entity_type>* m_pGlobalState;
};

#endif